/** 
    Copyright 2010 Henrik Andersson
    
    This file is part of Loopist.

    Loopist is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Loopist is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Loopist.  If not, see <http://www.gnu.org/licenses/>.
*/

package se.dinamic.loopist;
import android.util.Log;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import javax.microedition.khronos.opengles.GL10;

public class WaveformTexture implements Track.Listener {
	private final static int TEXTURE_WIDTH=64;
	private final static int TEXTURE_HEIGHT=512;
	private static ByteBuffer _buffer=null;
	private float _maxAverageValue=0;
	private final LinkedList<ByteBuffer> _updateQueue=new LinkedList<ByteBuffer>();
	private byte [] _waveformColor;
	private boolean _isWiped=true;
	
	private static float TEXTURED_PLANE_VERTICES[] = {
		0.0f, 0.0f,  0.0f,	
		1.0f, 0.0f,  0.0f,
		0.0f,  1.0f,  0.0f,
		1.0f,  1.0f,  0.0f
	};
	
	private static float TEXTURED_PLANE_VERTEX_COLORS[] = {
		1.0f,1.0f,1.0f,0.5f,
		1.0f,1.0f,1.0f,0.5f,
		1.0f,1.0f,1.0f,0.5f,
		1.0f,1.0f,1.0f,0.5f
	};
	
	private static float TEXTURED_PLANE_TEXTURE_COORDS[] = {
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 0.0f,
		0.0f, 1.0f
	};
	
	private static FloatBuffer _vaTexturedPlane = null;
	private static FloatBuffer _tcTexturedPlane = null;
	private static FloatBuffer _vcTexturedPlane = null;
	
	/** The generated texture id */
	private int _textureId=0;
	
	/** Number of total frames per whole track */
	private int _totalFramesPerTrack=0;
	
	/** How many frames to average for 1 px line in texture */
	private int _framesPerTextureLine=0;
	
	/** Reminder of where to start putting averaged samples into texture... */
	private int _averagedSampleTextureLine=0;
	
	private ShortBuffer _storedSamples;		 // Needed???
	
	private int _textureYOffset=0;
	
	
	public WaveformTexture(int frames,int[] color) {
		_waveformColor=new byte[3];
		_waveformColor[0]=(byte)color[0];
		_waveformColor[1]=(byte)color[1];
		_waveformColor[2]=(byte)color[2];
		_isWiped=true;
		_totalFramesPerTrack=frames;
		_framesPerTextureLine=(_totalFramesPerTrack/TEXTURE_HEIGHT);
		_storedSamples = ShortBuffer.allocate( 0 );
		Log.v("Loopist","WaveformTexture.WaveformTexture("+frames+") "+_framesPerTextureLine+" samples for averaging one lines in texture...");
		
		// Initialization of static data, only done once...
		if( _buffer == null ) {
			_buffer=ByteBuffer.allocateDirect(TEXTURE_HEIGHT*TEXTURE_WIDTH*4);
			_buffer.order(ByteOrder.nativeOrder());
			
			_tcTexturedPlane = ByteBuffer.allocateDirect(TEXTURED_PLANE_TEXTURE_COORDS.length<<2 ).order(ByteOrder.nativeOrder()).asFloatBuffer();
			_tcTexturedPlane.put( TEXTURED_PLANE_TEXTURE_COORDS );
			_tcTexturedPlane.position(0);
			
			_vaTexturedPlane = ByteBuffer.allocateDirect(TEXTURED_PLANE_VERTICES.length<<2 ).order(ByteOrder.nativeOrder()).asFloatBuffer();
			_vaTexturedPlane.put( TEXTURED_PLANE_VERTICES );
			_vaTexturedPlane.position(0);
			
			_vcTexturedPlane = ByteBuffer.allocateDirect(TEXTURED_PLANE_VERTEX_COLORS.length<<2 ).order(ByteOrder.nativeOrder()).asFloatBuffer();
			_vcTexturedPlane.put( TEXTURED_PLANE_VERTEX_COLORS );
			_vcTexturedPlane.position(0);
		}
	}
		
	
	/** Render a plane with the texture */
	public void render(GL10 gl) {
		if( !_isWiped ) {
			gl.glPushMatrix();
			gl.glEnable(GL10.GL_TEXTURE_2D);
			gl.glBindTexture(GL10.GL_TEXTURE_2D, _textureId);
			gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
			// Render tile textured plane..
			gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaTexturedPlane );
			gl.glTexCoordPointer(2, gl.GL_FLOAT, 0, _tcTexturedPlane);
			gl.glColorPointer(4, gl.GL_FLOAT, 0, _vcTexturedPlane);
			gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, _vaTexturedPlane.capacity()/3 );
			gl.glTranslatef(0.0f,0.1f,0.0f);
			gl.glScalef(1.0f,0.8f,1.0f);
			gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, _vaTexturedPlane.capacity()/3 );
			gl.glPopMatrix();
		}
	}
	
	/** Generates texture from initial buffer data... */
	public void finalize(GL10 gl) {
		int [] id = new int[1];
		gl.glGenTextures( 1, id, 0); 
		gl.glBindTexture(GL10.GL_TEXTURE_2D, id[0] );
		gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, _buffer);
		gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
		gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); 
		_textureId=id[0];
		id=null;
	}
	
	
	/** Add samples and averages to update lines in texture...
		@param samples A buffer with samples to add to waveform texture...
	*/
	public void addSamples( ShortBuffer samples )  { 
		int lcnt=(int)( ( _storedSamples.capacity() + samples.capacity()) / _framesPerTextureLine);
		samples.position(0);
		if(lcnt > 0 ) {
			
			FloatBuffer averageBuffer=FloatBuffer.allocate(lcnt);
			ByteBuffer  waveformPart = ByteBuffer.allocateDirect((TEXTURE_WIDTH*lcnt)*4);
			for( int i=0;i<lcnt;i++) {
				int samplesProcessed=0;
				
				long sum=0;
				
				// Special case, average including samples in _storedSamples
				if( i==0 && _storedSamples.limit() > 0 ) { 
					for(int s=0;s<_storedSamples.capacity();s++) {
						sum+=Math.abs(_storedSamples.get() );
						samplesProcessed++;
					}
					_storedSamples=null;
				}
				
				// Now sum up samples from argument...
				for( int s=0;s< (_framesPerTextureLine-samplesProcessed);s++) {
					sum+=Math.abs(samples.get() );
					samplesProcessed++;
				}
				
				// Let's put the average sample to buffer
				//Log.v("Loopist"," sum = "+sum+" averaged "+ sum/_framesPerTextureLine);
				float value=( (sum/_framesPerTextureLine)/32768.0f) ;
				_maxAverageValue = Math.max( _maxAverageValue, value );
			
				averageBuffer.put( value );
				
			}
			
			
			// Lets render a waveform part image to copy into orginal texture
			Log.v("Loopist","WaveformTexture.addSamples() "+averageBuffer.capacity()+" averaged lines to added to texture...");
			for(int f=0;f<waveformPart.capacity();f++)
				waveformPart.put((byte)0);
			float sampleScale =1.0f/_maxAverageValue;
			Log.v("Loopist","WaveformTexture.addSampels() Averaged sample scale "+ sampleScale);
				
			for( int y=0;y<averageBuffer.capacity();y++) {
				float avg=averageBuffer.get(y);
				//Log.v("Loopist","Averaged sample"+s+" = " +avg);
				int x=TEXTURE_WIDTH/2;
				if( avg>0 )
					x= (int) ( x - ((TEXTURE_WIDTH*(avg*sampleScale) ))/2.0);
				
				int offset=( (TEXTURE_WIDTH*y) + x) *4;
				
				waveformPart.position(offset);
				
				// Add pixels for line...
				for(int p=0;p< (TEXTURE_WIDTH-(x*2) ) ;p++) {
					waveformPart.put(_waveformColor[0]); waveformPart.put(_waveformColor[1]); waveformPart.put(_waveformColor[2]); waveformPart.put((byte)255);
				}
			}
			
			// Now lets render the waveformat buffer into waveform texture
			waveformPart.position(0);
			synchronized(_updateQueue) {
				_updateQueue.add( waveformPart );
			}
			
			
			// Free mem
			averageBuffer=null;
		} 
		
		
		
		// store remaining samples to next round...
		//_storedSamples = ShortBuffer.wrap( samples.array(), lcnt*_framesPerTextureLine, samples.capacity()-(lcnt*_framesPerTextureLine) );
		//Log.v("Loopist","WaveformTexture.addSamples() "+_storedSamples.limit()+" stored samples for next time...");
	}
	
	/** Check for wave data to update texture with...*/
	public void update(GL10 gl) {
		ByteBuffer waveformPart =null;
		synchronized(_updateQueue) { 
			waveformPart = _updateQueue.poll();
		}
		if( waveformPart != null ) {
			_isWiped = false;
		
			waveformPart.position(0);
			int lcnt=(waveformPart.capacity()/4)/TEXTURE_WIDTH;
			Log.v("Loopist","WaveformTexture.udpate() update texure at "+_textureYOffset);
			gl.glBindTexture(GL10.GL_TEXTURE_2D, _textureId);
			gl.glTexSubImage2D( GL10.GL_TEXTURE_2D, 0, 
				0,_textureYOffset, 
				TEXTURE_WIDTH, lcnt, 
				GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, waveformPart );
			
			_textureYOffset=(_textureYOffset+lcnt)%TEXTURE_HEIGHT;
		}
	}
	
	/*
	 * Track.Listener
	 */
	public void onWipe() {
		// Clear the whole texture
		Log.v("Loopist","WaveformTexture.onWipe() Track announced that it got wiped...");
		_isWiped=true;
	}
	
	public void onPutSamples(ShortBuffer samples) {
		// samples has been added to track.. lets average and put into waveform..
		Log.v("Loopist","WaveformTexture.onPutSamples() Track announced "+samples.capacity()+" samples was added");
		addSamples(samples);
	}
	public void onRecordPrepare() {
		_textureYOffset=0;
	}
	
}